<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JS  闭包 和 变量的作用域 </title>
    <!-- <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
    <script src="http://cdn.bootcss.com/uikit/2.25.0/js/uikit.js"></script> -->
    <script>
       // 参考文档 http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
       // 作为对比的几种常规写法 
       // 在JS中一切都是对象，函数是对象的一中

       function Circle(r) {   // 第一种使用prototype 这种对象方法
            this.r = r;
       }
       Circle.PI = 3.14159;
       Circle.prototype.area = function (){
           return Circle.PI * this.r * this.r;
       }
       
       var CircleTwo = function (){    // 第二种返回一个对象的属性方法计算 
            var obj = new Object();  
            obj.PI = 3.14159;  
                
            obj.area = function( r ) {  
                return this.PI * r * r;  
            }  
            return obj;  
       }

       var CircleFour = {   // 第四种 换了一种新建对象的方式 依然使用的类方法  
           "PI":3.14159,
           "area":function (r) {
               return this.PI * r * r;
           }
       }

       // 第五种 换了 一种新的新建对象的方式 还是使用的类方法 改方法能正常使用 
       var CircleFive = new Function("this.PI = 3.14159;this.area = function( r ) {return r*r*this.PI;}");  

       // 测试每种方法都是可行的
       function testCommonUseMethod(){
          var c = new Circle(1.0);
          alert(c.area());

          var c_Two = new CircleTwo();
          alert(c_Two.area(1.0))

           // 第三种 直接使用新建对象的方法 
           var circleThree = new Object();  
           circleThree.PI = 3.14159;  
           circleThree.Area = function( r ) {  
                return this.PI * r * r;  
           }  
           alert( circleThree.Area( 1.0 ) );  

           alert(CircleFour.area(1.0))
           alert(CircleFour.area(2.0))

           /*
           因为在Javascript中每个函数都有一个Portotype属性，而对象没有。添加两个方法，分别直接添加和添加打破Prototype上面，来看下调用情况。
           分析结果如下：
 　　       1、不使用prototype属性定义的对象方法，是静态方法，只能直接用类名进行调用！另外，此静态方法中无法使用this变量来调用对象其他的属性！
　　        2、使用prototype属性定义的对象方法，是非静态方法，只有在实例化后才能使用！其方法内部可以this来引用对象自身中的其他属性！
           */
       }


       // 现在测试 函数对象中的变量的作用域 
       var dom = function (){
           var Name = "Default"
           this.Sex = "Boy"
           this.success = function (){
               alert("SUCCESS")
           }
       }

       // 都提示 undefined 的错误 
       /*
       答案是两个都显示Undefined,为什么呢？这是由于在Javascript中每个function都会形成一个作用域，
       而这些变量声明在函数中，所以就处于这个函数的作用域中，外部是无法访问的。要想访问变量，就必须new一个实例出来。
       */
       function testFucObjVariable(){
           alert(dom.Name)
           alert(dom.Sex)
        //    dom.success()
           html.Success()
           alert(html.Name)
           html.Say()

           htmlTwo.Success()
           alert(htmlTwo.Name)
           htmlTwo.Say()

           var s = new htmlThree.Success()
           s.Show()
       }

       var html = {
           Name:'html_Object',
           Success:function(){
               this.Say = function () {
                   alert("html success say Hello De Long")
               }
               alert("Obj Success")
           }
       }


       // html_two 和 html 其实同样的 第一种更像是一种语法糖
       var htmlTwo = new Object()
       htmlTwo.Name = 'htmlTwo_Object'
       htmlTwo.Success = function () {
           this.Say = function () {
               alert("htmlTwo_Object say Hello ,World")
           }
           alert("htmlTwo_Object Objc Success")
       }

       var htmlThree = {
           Name:'html_three_Object',
           Success:function(){
               this.Say = function () {
                   alert("html_Three success say Hello De Long")
               }
               alert("html three Obj Success")
           }
       }

       htmlThree.Success.prototype.Show = function (){
           alert ("Show Haha ")
       }

       // 闭包的用途 
       /*
       事实上，通过使用闭包，我们可以做很多事情。比如模拟面向对象的代码风格；更优雅，更简洁的表达出代码；在某些方面提升代码的执行效率。
       1、匿名自执行函数
        　　我们知道所有的变量，如果不加上var关键字，则默认的会添加到全局对象的属性上去，这样的临时变量加入全局对象有很多坏处，
        比如：别的函数可能误用这些变量； ！！！！  很有可能因为误用产生一些未知的bug
        造成全局对象过于庞大，影响访问速度(因为变量的取值是需要从原型链上遍历的)。
        除了每次使用变量都是用var关键字外，我们在实际情况下经常遇到这样一种情况，即有的函数只需要执行一次，其内部变量无需维护，
        比如UI的初始化，那么我们可以使用闭包：
       */
       var data = {
           table:[],
           tree:{}
       }

       /*
       我们创建了一个匿名的函数，并立即执行它，由于外部无法引用它内部的变量，
       因此在函数执行完后会立刻释放资源，关键是不污染全局对象。
       */ 
    //    (function(dm){    
    //         for(var i = 0; i < dm.table.rows; i++) {    
    //             var row = dm.table.rows[i];    
    //             for(var j = 0; j < row.cells; i++){    
    //                 drawCell(i, j);    
    //             }    
    //         }    
    //    })(data);  
       
       /*
       2、结果缓存
        我们开发中会碰到很多情况，设想我们有一个处理过程很耗时的函数对象，每次调用都会花费很长时间，
        那么我们就需要将计算出来的值存储起来，当调用这个函数的时候，首先在缓存中查找，如果找不到，
        则进行计算，然后更新缓存并返回值，如果找到了，直接返回查找到的值即可。闭包正是可以做到这一点，
        因为它不会释放外部的引用，从而函数内部的值可以得以保留。
       */
       var CachedSearchBox = (function(){
           var cache = {},
               count = [];
            return {
                attachSearchBox: function (dsid){
                    if (dsid in cache){
                        console.log("get cache" + dsid)
                        return cache[dsid]
                    }
                    var fsb = "hello" + dsid
                    cache[dsid] = fsb;//更新缓存    
                    if(count.length > 100){//保正缓存的大小<=100    
                        delete cache[count.shift()];    
                    }    
                    return fsb;
                },
                clearSearchBox: function (dsid){
                    if(dsid in cache){    
                        cache[dsid].clearSelection();      
                    }   
                }
            }
       })();

       /*
       封装  经过这样处理后 name 变成了私有变量 
       */
       var person = function () {
          // 
          var name = "default";
          return {
              getName: function(){
                  return name;
              },
              setName: function(newName){
                  name = newName;
              }
          }
       }();
       /*
       实现类和继承
       */
       function Person(){    
            var name = "default";       
            
            return {    
              getName : function(){    
                return name;    
              },    
              setName : function(newName){    
                name = newName;    
             }    
           }    
        };   
       var p = new Person()
       p.setName("Tom")
       alert (p.getName())
       var Jack = function (){
            Jack.prototype = new Person()
            Jack.prototype.Say = function (){
                alert("hello ,my name is Jack")
            }
       }


       // 测试闭包的 结果缓存
       function testClosureCacheResult(){
           alert(CachedSearchBox.attachSearchBox("input"))
           alert(CachedSearchBox.attachSearchBox("input"))
           alert(CachedSearchBox.attachSearchBox("index"))
           alert(CachedSearchBox.attachSearchBox("book"))
       }

       // 测试封装
       function testClosurePackaging(){
         alert(person.name);//直接访问，结果为undefined    
         alert(person.getName());    
         person.setName("abruzzi");    
         alert(person.getName());  
       }
       
       // 测试继承的类   ！！注意笔者认为
       function testClosureInherit(){
            var j = new Jack();
            j.setName("Jack");
            j.Say();
            alert(j.getName());
       }

    </script>
</head>
<body>
    <button onclick="testCommonUseMethod()">测试常用的方法</button><br/>  <!-- 测试常用的方法 -->
    <button onclick="testFucObjVariable()">测试函数作用域</button><br/>  <!-- 测试函数作用域  都显示-->
    <button onclick="testClosureCacheResult()">测试闭包缓存结果</button><br/>  <!-- 测试函数作用域  都显示-->
    <button onclick="testClosurePackaging()">测试闭包封装</button><br/>  <!-- 测试函数作用域  都显示-->
    <button onclick="testClosureInherit()">测试闭包继承</button><br/>  <!-- 测试函数作用域  都显示-->
</body>
</html>